package net.siufung.core.utils;

import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

/**
 * 路径匹配
 * @author 陈建峰
 * @since 2022/6/9 4:10 下午
 */
public class TopicUtil {

    private final static PathMatcher PATH_MATCHER = new AntPathMatcher();

    /**
     * 匹配Topic
     *
     * <pre>
     *     match("/test/*","/test/1"); // true
     *     match("/test/*","/test/1/2"); // false
     *     match("/test/**","/test/1/2"); // true
     * </pre>
     *
     * @param pattern 匹配模版
     * @param topic   要匹配的Topic
     * @return 是否匹配
     */
    public static boolean match(String pattern, String topic) {
        if (pattern.equals(topic)) {
            return true;
        }
        if (!pattern.contains(StringPool.ASTERISK)
                && !pattern.contains(StringPool.HASH) && !pattern.contains(StringPool.PLUS)
                && !pattern.contains(StringPool.LEFT_BRACE)) {
            return false;
        }
        return PATH_MATCHER.match(pattern
                .replace(StringPool.HASH, StringPool.DOUBLE_ASTERISK)
                .replace(StringPool.PLUS, StringPool.ASTERISK), topic);
    }

    /**
     * 根据模版从url上提取变量,如果提取出错则返回空Map
     *
     * <pre>
     *   getPathVariables("/test/**","/test/test123");
     * </pre>
     *
     * @param template 模版
     * @param topic    要提取的topic
     * @return 提取结果
     */
    public static Map<String, String> getPathVariables(String template, String topic) {
        try {
            return PATH_MATCHER.extractUriTemplateVariables(template, topic);
        } catch (Exception e) {
            return Collections.emptyMap();
        }
    }

    /**
     * 分隔topic
     *
     * @param topic topic
     * @return 分隔结果
     */
    public static String[] split(String topic) {
        return topic.split(StringPool.SLASH);
    }

    /**
     * 匹配字符串
     *
     * @param str 字符串
     * @param pattern 规则
     * @return true/false
     */
    private static Boolean matchStrings(String str, String pattern) {
        return str.equals(pattern)
                || StringPool.ASTERISK.equals(pattern)
                || StringPool.ASTERISK.equals(str);
    }

    /**
     * 匹配字符串集合
     *
     * @param pattern 匹配集合
     * @param topicParts topic集合
     * @return true/false
     */
    public static Boolean match(String[] pattern, String[] topicParts) {
        if (pattern.length == 0 && topicParts.length == 0) {
            return true;
        }
        int pattIdxStart = 0;
        int pattIdxEnd = pattern.length - 1;
        int pathIdxStart = 0;
        int pathIdxEnd = topicParts.length - 1;
        while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
            String patDir = pattern[pattIdxStart];
            //匹配多层
            if (StringPool.DOUBLE_ASTERISK.equals(patDir)) {
                break;
            }
            if (!matchStrings(patDir, topicParts[pathIdxStart])) {
                return false;
            }
            pattIdxStart++;
            pathIdxStart++;
        }
        if (pathIdxStart > pathIdxEnd) {
            if (pattIdxStart > pattIdxEnd) {
                return (pattern[pattern.length - 1].equals(StringPool.SLASH) == topicParts[topicParts.length - 1].equals("/"));
            }

            if (pattIdxStart == pattIdxEnd && pattern[pattIdxStart].equals(StringPool.ASTERISK) && topicParts[topicParts.length - 1].equals("/")) {
                return true;
            }
            for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
                if (!pattern[i].equals(StringPool.DOUBLE_ASTERISK)) {
                    return false;
                }
            }
            return true;
        } else if (pattIdxStart > pattIdxEnd) {
            // String not exhausted, but pattern is. Failure.
            return false;
        } else if (StringPool.DOUBLE_ASTERISK.equals(topicParts[pattIdxStart])) {
            // Path start definitely matches due to "**" part in pattern.
            return true;
        }
        // up to last '**'
        while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
            String patDir = pattern[pattIdxEnd];
            if (patDir.equals(StringPool.DOUBLE_ASTERISK)) {
                break;
            }
            if (!matchStrings(patDir, topicParts[pathIdxEnd])) {
                return false;
            }
            pattIdxEnd--;
            pathIdxEnd--;
        }
        if (pathIdxStart > pathIdxEnd) {
            // String is exhausted
            for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
                if (!pattern[i].equals(StringPool.DOUBLE_ASTERISK)) {
                    return false;
                }
            }
            return true;
        }

        while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
            int patIdxTmp = -1;
            for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
                if (pattern[i].equals(StringPool.DOUBLE_ASTERISK)) {
                    patIdxTmp = i;
                    break;
                }
            }
            if (patIdxTmp == pattIdxStart + 1) {
                // '**/**' situation, so skip one
                pattIdxStart++;
                continue;
            }
            // Find the pattern between padIdxStart & padIdxTmp in str between
            // strIdxStart & strIdxEnd
            int patLength = (patIdxTmp - pattIdxStart - 1);
            int strLength = (pathIdxEnd - pathIdxStart + 1);
            int foundIdx = -1;

            strLoop:
            for (int i = 0; i <= strLength - patLength; i++) {
                for (int j = 0; j < patLength; j++) {
                    String subPat = pattern[pattIdxStart + j + 1];
                    String subStr = topicParts[pathIdxStart + i + j];
                    if (!matchStrings(subPat, subStr)) {
                        continue strLoop;
                    }
                }
                foundIdx = pathIdxStart + i;
                break;
            }

            if (foundIdx == -1) {
                return false;
            }

            pattIdxStart = patIdxTmp;
            pathIdxStart = foundIdx + patLength;
        }

        for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
            if (!pattern[i].equals(StringPool.DOUBLE_ASTERISK)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 展开topic
     * <p>
     * before:
     * <pre>
     *      /test/a,b,v/*
     *  </pre>
     * after:
     * <pre>
     *     /test/a/*
     *     /test/b/*
     *     /test/v/*
     * </pre>
     *
     * before:
     * <pre>
     *     /test/{id}
     * </pre>
     * after:
     * <pre>
     *    /test/*
     * </pre>
     *
     * @param topic topic
     * @return 展开的topic集合
     */
    public static List<String> expand(String topic) {
        if (!topic.contains(StringPool.COMMA)&&!topic.contains(StringPool.LEFT_BRACE)) {
            return Collections.singletonList(topic);
        }
        if (topic.startsWith(StringPool.SLASH)) {
            topic = topic.substring(1);
        }
        String[] parts = topic.split(StringPool.SLASH, 2);

        String first = parts[0];
        List<String> expands = new ArrayList<>();

        if (parts.length == 1) {
            for (String split : first.split(StringPool.COMMA)) {
                if (split.startsWith(StringPool.LEFT_BRACE) && split.endsWith(StringPool.RIGHT_BRACE)) {
                    split = StringPool.ASTERISK;
                }
                expands.add(StringPool.SLASH + split);
            }
            return expands;
        }

        List<String> nextTopics = expand(parts[1]);

        for (String split : first.split(StringPool.COMMA)) {
            if (split.startsWith(StringPool.LEFT_BRACE) && split.endsWith(StringPool.RIGHT_BRACE)) {
                split = StringPool.ASTERISK;
            }
            for (String nextTopic : nextTopics) {
                StringJoiner joiner = new StringJoiner("");
                joiner.add(StringPool.SLASH);
                joiner.add(split);
                if (!nextTopic.startsWith(StringPool.SLASH)) {
                    joiner.add(StringPool.SLASH);
                }
                joiner.add(nextTopic);
                expands.add(joiner.toString());
            }

        }

        return expands;
    }

}
